package com.sc.demo2.common.util;


import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.security.*;
import com.itextpdf.tool.xml.XMLWorkerHelper;
import com.sc.generatorcode.utils.FileUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.*;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.util.HashMap;
import java.util.Map;

public class PDFUtil {
    /**
     * 1.根据freemarker模板生成html
     * 2.根据html生成pdf
     * 3.对pdf进行签章和手势签名
     * @param pdfFilePath
     * @param ftlDirPath
     * @param htmlFilePath
     * @param ftlFileName
     * @param docurx
     * @param docury
     * @param data
     * @return
     * @throws TemplateException
     * @throws IOException
     * @throws Exception
     */
    public static void createPDF(String pdfFilePath,
                                   String ftlDirPath,
                                   String htmlFilePath,
                                   String ftlFileName,
                                   Float docurx,
                                   Float docury,
                                   Map data) throws TemplateException, IOException, Exception {
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);

        cfg.setDirectoryForTemplateLoading(new File(ftlDirPath));

        // 设置编码
        cfg.setDefaultEncoding("UTF-8");

        // 从指定的模板目录中加载对应的模板文件
        Template temp = cfg.getTemplate(ftlFileName);

        File htmlFile = new File(htmlFilePath);
        if (!htmlFile.exists()) {
            htmlFile.createNewFile();
        }

        Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(htmlFile), "UTF-8"));

        temp.process(data, out);

        Document document = null;

        if (docurx == null || docury == null) {

            // 默认设置
            document = new Document(PageSize.A4); // 横向打印

        } else {

            document = new Document(new RectangleReadOnly(docurx, docury));

        }

        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(pdfFilePath));

        document.open();

        XMLWorkerHelper.getInstance().parseXHtml(writer, document, new FileInputStream(htmlFilePath), Charset.forName("UTF-8"));

        document.close();
    }


    /**
     * 根据PDF模板填充表单数据，并生成新的PDF
     * @param pdfTemplateFilePath
     * @param pdfSignFilePath
     * @param formData
     * @param formFlattening true:生成的pdf表单不能再编辑；false:生成pdf表单可以再编辑
     * @throws Exception
     */
    public static void createPDF(String pdfTemplateFilePath,
                                  String pdfSignFilePath,
                                  HashMap<String, String> formData,
                                  boolean formFlattening) throws Exception {
        PdfReader reader = null;
        ByteArrayOutputStream bos = null;
        PdfStamper pdfStamper = null;
        FileOutputStream fos = null;
        try {
            // 读取PDF模版文件
            reader = new PdfReader(pdfTemplateFilePath);

            // 输出流
            bos = new ByteArrayOutputStream();

            // 构建PDF对象
            pdfStamper = new PdfStamper(reader, bos);

            // 获取表单数据
            AcroFields form = pdfStamper.getAcroFields();

            // 使用中文字体 使用 AcroFields填充值的不需要在程序中设置字体，在模板文件中设置字体为中文字体 Adobe 宋体 std L
            BaseFont bfChinese = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            form.addSubstitutionFont(bfChinese);

            // 表单赋值
            for (String key : formData.keySet()) {
                form.setField(key, formData.get(key));
                form.setFieldProperty(key, "textfont", bfChinese, null);
            }


            pdfStamper.setFormFlattening(formFlattening);
            pdfStamper.close();

            // 保存文件
            fos = new FileOutputStream(pdfSignFilePath);
            fos.write(bos.toByteArray());
            fos.flush();
        } finally {
            if (null != fos) {
                try {
                    fos.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            if (null != bos) {
                try {
                    bos.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            if (null != reader) {
                try {
                    reader.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 签名，使用坐标插入签名图片
     * @param src
     * @param dest
     * @param p12Stream
     * @param pwd
     * @param reason
     * @param location
     * @param signatureImagePath
     * @param llx
     * @param lly
     * @param urx
     * @param ury
     * @param page
     * @param fieldName
     * @throws GeneralSecurityException
     * @throws IOException
     * @throws DocumentException
     */
    public static void sign(InputStream src,
                            OutputStream dest,
                            InputStream p12Stream,
                            String pwd,
                            String reason,
                            String location,
                            String signatureImagePath,
                            float llx,
                            float lly,
                            float urx,
                            float ury,
                            int page,
                            String fieldName)
            throws GeneralSecurityException, IOException, DocumentException {

        char[] password = pwd.toCharArray();

        // 读取keystore，获得私钥和证书链
        KeyStore ks = KeyStore.getInstance("PKCS12");
        ks.load(p12Stream, password);

        String alias = ks.aliases().nextElement();
        PrivateKey pk = (PrivateKey) ks.getKey(alias, password);
        Certificate[] chain = ks.getCertificateChain(alias);

        PdfReader reader = new PdfReader(src);

        PdfStamper stamper = PdfStamper.createSignature(reader, dest, '\0', null, true);

        // 获取数字签章属性对象，设定数字签章的属性
        PdfSignatureAppearance appearance = stamper.getSignatureAppearance();

        appearance.setReason(reason);

        appearance.setLocation(location);

        // 设置签名的位置，页码，签名域名称，多次追加签名的时候，签名预名称不能一样
        // 签名的位置，是图章相对于pdf页面的位置坐标，原点为pdf页面左下角
        // 四个参数的分别是，图章左下角x，图章左下角y，图章右上角x，图章右上角y
        appearance.setVisibleSignature(new Rectangle(llx, lly, urx, ury), page, fieldName);

        Image image = Image.getInstance(signatureImagePath);
        appearance.setSignatureGraphic(image);
        appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);

        // 设置图章的显示方式，如下选择的是只显示图章（还有其他的模式，可以图章和签名描述一同显示）
        appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);


        // 摘要算法
        ExternalDigest digest = new BouncyCastleDigest();

        // 签名算法
        ExternalSignature signature = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, null);

        // 调用itext签名方法完成pdf签章
        MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, MakeSignature.CryptoStandard.CMS);
    }


    /**
     * 签名
     * @param pdfFilePath
     * @param pdfSignFilePath
     * @param p12FilePath
     * @param pwd
     * @param reason
     * @param location
     * @param fieldName
     * @param signatureImagePath
     * @param append
     * @throws GeneralSecurityException
     * @throws IOException
     * @throws DocumentException
     */
    public static void sign(String pdfFilePath,
                            String pdfSignFilePath,
                            String p12FilePath,
                            String pwd,
                            String reason,
                            String location,
                            String fieldName,
                            String signatureImagePath,
                            boolean append) throws GeneralSecurityException, IOException, DocumentException {

        InputStream src = new FileInputStream(pdfFilePath);
        OutputStream dest = new FileOutputStream(pdfSignFilePath);
        InputStream p12Stream =  new FileInputStream(p12FilePath);

        char[] password = pwd.toCharArray();

        // 读取keystore，获得私钥和证书链
        KeyStore ks = KeyStore.getInstance("PKCS12");
        ks.load(p12Stream, password);

        String alias = ks.aliases().nextElement();
        PrivateKey pk = (PrivateKey) ks.getKey(alias, password);
        Certificate[] chain = ks.getCertificateChain(alias);

        Image signatureGraphic = Image.getInstance(signatureImagePath);

        PdfReader reader = new PdfReader(src);

        try {
            PdfStamper stamper = PdfStamper.createSignature(reader, dest, '\0', null, append);

            // 获取数字签章属性对象，设定数字签章的属性
            PdfSignatureAppearance appearance = stamper.getSignatureAppearance();

            appearance.setReason(reason);

            appearance.setLocation(location);

            appearance.setVisibleSignature(fieldName);

            appearance.setSignatureGraphic(signatureGraphic);

            appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);

            // 设置图章的显示方式，如下选择的是只显示图章（还有其他的模式，可以图章和签名描述一同显示）
            appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);

            // 摘要算法
            ExternalDigest digest = new BouncyCastleDigest();

            // 签名算法
            ExternalSignature signature = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, null);

            // 调用itext签名方法完成pdf签章
            MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, MakeSignature.CryptoStandard.CMS);
        }finally {
            if(dest != null){
                dest.flush();
                dest.close();
            }

            if(src != null){
                src.close();
            }

            if(p12Stream != null){
                p12Stream.close();
            }

            if (null != reader) {
                reader.close();
            }
        }
    }


    public static String getResourcePath() {
        StringBuilder sb = new StringBuilder();
        sb.append(getBasicProjectPath()).append("resources").append(File.separator);
        return sb.toString();
    }



    public static String getBasicProjectPath() {
        String path = new File(FileUtil.class.getClassLoader().getResource("").getFile()).getPath() + File.separator;
        StringBuilder sb = new StringBuilder();
        sb.append(path.substring(0, path.indexOf("target"))).append("src").append(File.separator).append("main").append(File.separator);
        return sb.toString();
    }


    public static String getResourcePath4test() {
        StringBuilder sb = new StringBuilder();
        sb.append(getBasicProjectPath4test()).append("resources").append(File.separator);
        return sb.toString();
    }

    public static String getBasicProjectPath4test() {
        String path = new File(FileUtil.class.getClassLoader().getResource("").getFile()).getPath() + File.separator;
        StringBuilder sb = new StringBuilder();
        sb.append(path.substring(0, path.indexOf("target"))).append("src").append(File.separator).append("test").append(File.separator);
        return sb.toString();
    }
}
